/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.connection.parser.json;

import com.google.gjson.JSONArray;
import com.google.gjson.JSONException;
import com.google.gjson.JSONObject;
import com.hercules.android.receiver.connection.parser.CommandParsingException;
import com.hercules.android.receiver.connection.parser.MalformedCommandException;
import com.hercules.android.receiver.model.commands.XRECallCommand;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.commands.XREConnectCommand;
import com.hercules.android.receiver.model.commands.XREConnectRejectedCommand;
import com.hercules.android.receiver.model.commands.XREDeleteCommand;
import com.hercules.android.receiver.model.commands.XREGetCommand;
import com.hercules.android.receiver.model.commands.XRENewCommand;
import com.hercules.android.receiver.model.commands.XRERedirectCommand;
import com.hercules.android.receiver.model.commands.XRERestartCommand;
import com.hercules.android.receiver.model.commands.XRESetCommand;
import com.hercules.android.receiver.model.commands.XREShutdownCommand;
import com.hercules.android.receiver.model.commands.XREStartTimerCommand;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Represent parser for {@link XRECommand}.
 * <p>
 * AndroidAssembly2 this class was added in Android Assembly 2
 *
 * @author 7realm
 * @version 1.0
 */
public class CommandParser extends ItemParser {
    /** Represents name of 'timestamp' field. */
    private static final String FIELD_TIMESTAMP = "timestamp";
    /** Represents name of 'commandIndex' field. */
    private static final String FIELD_COMMAND_INDEX = "commandIndex";
    /** Represents name of 'command' field. */
    private static final String FIELD_COMMAND_NAME = "command";
    /** Represents map of command name to command class. */
    private static final Map<String, Class<?>> COMMANDS = init();

    /**
     * Constructor for {@link CommandParser}.
     *
     * @param required if property is required
     */
    public CommandParser(boolean required) {
        super(required);
    }

    /**
     * Parses JSON key value.
     *
     * @param path the path to value
     * @param json the JSON
     * @param keyName the name of current JSON key
     * @param instance the instance of object, parsed value will be set to it
     * @param setterType describes how value should be set to instance
     * @return the parsed value
     * @throws CommandParsingException if error occurs while parsing
     */
    @Override
    public Object parse(StringBuilder path, JSONObject json, String keyName, Object instance, SetterType setterType)
        throws CommandParsingException {
        try {
            // get command name
            String commandName = getStringValue(json, FIELD_COMMAND_NAME, "Command does not have command name.");

            // create command instance
            Class<?> commandClass = COMMANDS.get(commandName);
            if (commandClass == null) {
                throw new MalformedCommandException("Not supported command name '" + commandName + "'. JSON '" + json
                    + "'.");
            }
            // because XRECommand is plain POJO we can use newInstance here
            XRECommand command = (XRECommand) commandClass.newInstance();

         // if this is not local command then get command index and timestamp
            if (path.length() == 0) {
                INTEGER_REQUIRED.parse(join(path, FIELD_COMMAND_INDEX), json, FIELD_COMMAND_INDEX, command,
                    SetterType.FIELD);
                INTEGER_REQUIRED.parse(join(path, FIELD_TIMESTAMP), json, FIELD_TIMESTAMP, command, SetterType.FIELD);
            } else {
                command.setCommandIndex(XRECommand.LOCAL_COMMAND_INDEX);
            }

            // clear path if this is needed
            path.setLength(0);

            Set<String> exceptionKeys = new HashSet<String>();
            exceptionKeys.add(FIELD_COMMAND_NAME);
            exceptionKeys.add(FIELD_COMMAND_INDEX);
            exceptionKeys.add(FIELD_TIMESTAMP);
            
            // parse map
            parseMap(join(path, commandName), json, command, SetterType.FIELD, exceptionKeys);

            // check if we need set value
            if (instance != null) {
                setValue(path, keyName, instance, command, setterType);
            }
            return command;
        } catch (IllegalAccessException e) {
            throw new CommandParsingException("Default constructor of command is not accessible. JSON '" + json + "'.",
                e);
        } catch (InstantiationException e) {
            throw new CommandParsingException("Failed to create command using default constructor. JSON '" + json
                + "'.", e);
        }
    }

    /**
     * Parses JSON array item value.
     *
     * @param path the path to value
     * @param jsonArray the JSON array
     * @param index the name of current item in array
     * @param instance the instance of object, parsed value will be set to it
     * @param setterType describes how value should be set to instance
     * @return the parsed value
     * @throws CommandParsingException if error occurs while parsing
     */
    @Override
    public Object parse(StringBuilder path, JSONArray jsonArray, int index, Object instance, SetterType setterType)
        throws CommandParsingException {
        try {
            return parse(path, jsonArray.get(index).isObject(), null, instance, setterType);
        } catch (JSONException e) {
            throw new MalformedCommandException("Item '" + path
                + "' expected to be JSON object. JSON array '" + jsonArray + "'.", e);
        }
    }

    /**
     * Initializes commands map.
     *
     * @return the map of commands
     */
    private static Map<String, Class<?>> init() {
        Map<String, Class<?>> map = new HashMap<String, Class<?>>();
        // connection related commands
        map.put(XREConnectCommand.COMMAND_NAME, XREConnectCommand.class);
        map.put(XREConnectRejectedCommand.COMMAND_NAME, XREConnectRejectedCommand.class);
        map.put(XRERedirectCommand.COMMAND_NAME, XRERedirectCommand.class);
        map.put(XRERestartCommand.COMMAND_NAME, XRERestartCommand.class);
        map.put(XREShutdownCommand.COMMAND_NAME, XREShutdownCommand.class);

        // object commands
        map.put(XRENewCommand.COMMAND_NAME, XRENewCommand.class);
        map.put(XREGetCommand.COMMAND_NAME, XREGetCommand.class);
        map.put(XRESetCommand.COMMAND_NAME, XRESetCommand.class);
        map.put(XREDeleteCommand.COMMAND_NAME, XREDeleteCommand.class);
        map.put(XRECallCommand.COMMAND_NAME, XRECallCommand.class);
        map.put(XREStartTimerCommand.COMMAND_NAME, XREStartTimerCommand.class);
        
        return map;
    }
}
